using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System.Reflection;
using WCS.Infrastructure.Attr;
using WCS.Infrastructure.EFCore;
using WCS.Infrastructure.Tools;
using WCS.Service.EFCore;

namespace WCS.Api.Admin.Filter
{
    // 全局注册：对除 [AllowAnonymous] 外的接口做“硬编码权限”校验
    public class StaticRoleAuthorizationFilter : IAsyncActionFilter
    {
        private readonly SysUserRoleEFCore _userRoleEFCore;
        private readonly SysRoleEFCore _roleEFCore;
        private readonly IMemoryCache _cache;

        // 控制器级默认权限（可逐步完善）
        private static readonly Dictionary<string, int[]> DefaultControllerRoleMap = new(StringComparer.OrdinalIgnoreCase)
        {
            // 组织架构：默认 2/3
            ["Organization"] = new[] { 2, 3 },
            // 支付：默认 2/3/5（店长可在自己门店范围内做有限操作）
            ["Payment"] = new[] { 2, 3, 5 },
            // 优惠券：默认 2/3
            ["Coupon"] = new[] { 2, 3 },
            // 用户：默认全放行给 B 端用户（2/3/4/5/6）
            ["User"] = new[] { 2, 3, 4, 5, 6 },
            // 日志：2/3
            ["Log"] = new[] { 2, 3 },
        };

        // 动作级覆盖（可逐步补充）
        private static readonly Dictionary<(string Controller, string Action), int[]> ActionOverrides
            = new(StringTupleComparer.OrdinalIgnoreCase)
        {
            // 示例：支付平台新增只能 2
            // { ("Payment", "AddPaymentPlatform"), new[] { 2 } },
            // 示例：获取团队成员列表，读放宽到 2/3/4/5
            // { ("Organization", "GetTeamMembersListAsync"), new[] { 2, 3, 4, 5 } },
        };

        public StaticRoleAuthorizationFilter(SysUserRoleEFCore userRoleEFCore,
                                             SysRoleEFCore roleEFCore,
                                             IMemoryCache cache)
        {
            _userRoleEFCore = userRoleEFCore;
            _roleEFCore = roleEFCore;
            _cache = cache;
        }

        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            // 1) 跳过匿名
            if (HasAllowAnonymous(context)) { await next(); return; }

            // 2) 获取 Controller / Action
            var cad = context.ActionDescriptor as ControllerActionDescriptor;
            var controller = cad?.ControllerName?.Replace("Controller", "") ?? "";
            var action = cad?.ActionName ?? "";
            var httpMethod = context.HttpContext.Request.Method?.ToUpperInvariant();

            // 3) 计算允许的角色集合（动作覆盖 > 控制器默认）
            int[] allowedRoleTypes = ResolveAllowedRoleTypes(controller, action);

            // 如果 Controller 不在映射里，默认放行（避免误伤；可改为默认拒绝）
            if (allowedRoleTypes.Length == 0)
            {
                await next();
                return;
            }

            // 4) 获取当前用户（只允许 B 端用户）
            var currentUser = context.HttpContext.Items["CurrentUser"];
            if (currentUser is not SysUser bUser)
            {
                context.Result = new ObjectResult(new { code = 403, message = "无权访问：仅限B端用户" }) { StatusCode = 403 };
                return;
            }
            var userId = bUser.Id;

            // 5) 提取业务范围（MerchantId / StoreId / CodeId）
            var scope = ResolveScope(context.ActionArguments, context);

            // 6) 查用户在该范围内的角色类型，是否命中允许集合
            var cacheKey = $"AuthR_{userId}_{scope.MerchantId}_{scope.StoreId}_{scope.CodeId}";
            var userRoleTypes = await _cache.GetOrCreateAsync(cacheKey, async e =>
            {
                e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30);

                // 读用户的所有关联（可按 scope 过滤）
                var query = _userRoleEFCore.QueryAll(r => r.UserId == userId);

                if (!string.IsNullOrEmpty(scope.MerchantId))
                    query = query.Where(r => r.MerchantId == scope.MerchantId);

                if (!string.IsNullOrEmpty(scope.StoreId))
                    query = query.Where(r => r.StoreId == scope.StoreId);

                if (!string.IsNullOrEmpty(scope.CodeId))
                    query = query.Where(r => r.CodeId == scope.CodeId);

                var list = await query.Select(r => r.RoleId).Distinct().ToListAsync();
                if (list.Count == 0) return Array.Empty<int>();

                var roleTypes = await _roleEFCore.QueryAll(rr => list.Contains(rr.Id))
                                                .Select(rr => rr.RoleType ?? -1).ToListAsync();
                return roleTypes.Where(x => x >= 0).Distinct().ToArray();
            });

            // 7) 店长/监事/收银员 应该具备“范围条件”时：如果没提供范围，给出更明确的提示（降低误伤）
            if ((allowedRoleTypes.Contains(4) || allowedRoleTypes.Contains(5) || allowedRoleTypes.Contains(6)) &&
                string.IsNullOrEmpty(scope.MerchantId) && string.IsNullOrEmpty(scope.StoreId) && string.IsNullOrEmpty(scope.CodeId))
            {
                // 这类角色通常需要指定范围
                // 这里不直接拒绝，仍按现有 userRoleTypes 判定。需要更严格时可开启强校验。
            }

            // 8) 命中判断
            bool allowed = userRoleTypes.Any(rt => allowedRoleTypes.Contains(rt));
            if (!allowed)
            {
                context.Result = new ObjectResult(new
                {
                    code = 403,
                    message = $"无权访问：需要角色[{string.Join("/", allowedRoleTypes)}]，当前范围未匹配到相应角色"
                })
                { StatusCode = 403 };
                return;
            }

            await next();
        }

        private static bool HasAllowAnonymous(ActionExecutingContext context)
        {
            var cad = context.ActionDescriptor as ControllerActionDescriptor;
            if (cad == null) return false;

            bool actionAnonymous = cad.MethodInfo.GetCustomAttributes(inherit: true)
                .Any(a => a is AllowAnonymousAttribute);
            if (actionAnonymous) return true;

            bool controllerAnonymous = cad.ControllerTypeInfo.GetCustomAttributes(inherit: true)
                .Any(a => a is AllowAnonymousAttribute);
            return controllerAnonymous;
        }

        private static int[] ResolveAllowedRoleTypes(string controller, string action)
        {
            if (ActionOverrides.TryGetValue((controller, action), out var ov)) return ov;
            if (DefaultControllerRoleMap.TryGetValue(controller, out var def)) return def;
            return Array.Empty<int>();
        }

        private static (string? MerchantId, string? StoreId, string? CodeId) ResolveScope(IDictionary<string, object?> args, ActionExecutingContext context)
        {
            string? merchantId = null, storeId = null, codeId = null;

            // 1) 从 Action 参数对象中解析（DTO）
            foreach (var kv in args)
            {
                var val = kv.Value;
                if (val == null) continue;

                var t = val.GetType();
                foreach (var p in t.GetProperties(BindingFlags.Public | BindingFlags.Instance))
                {
                    if (!p.CanRead) continue;
                    var name = p.Name;
                    if (string.Equals(name, "MerchantId", StringComparison.OrdinalIgnoreCase))
                        merchantId = merchantId ?? p.GetValue(val)?.ToString();
                    else if (string.Equals(name, "StoreId", StringComparison.OrdinalIgnoreCase))
                        storeId = storeId ?? p.GetValue(val)?.ToString();
                    else if (string.Equals(name, "CodeId", StringComparison.OrdinalIgnoreCase))
                        codeId = codeId ?? p.GetValue(val)?.ToString();
                }
            }

            // 2) 兜底从 QueryString 解析
            var query = context.HttpContext.Request.Query;
            merchantId ??= query.TryGetValue("merchantId", out var mv) ? mv.ToString() : null;
            storeId ??= query.TryGetValue("storeId", out var sv) ? sv.ToString() : null;
            codeId ??= query.TryGetValue("codeId", out var cv) ? cv.ToString() : null;

            return (merchantId, storeId, codeId);
        }

        private class StringTupleComparer : IEqualityComparer<(string, string)>
        {
            public static readonly StringTupleComparer OrdinalIgnoreCase = new();

            public bool Equals((string, string) x, (string, string) y)
                => string.Equals(x.Item1, y.Item1, StringComparison.OrdinalIgnoreCase)
                && string.Equals(x.Item2, y.Item2, StringComparison.OrdinalIgnoreCase);

            public int GetHashCode((string, string) obj)
                => StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Item1)
                 ^ StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Item2);
        }
    }
}